package com.wan.core.timer;

import com.wan.core.shutdown.ShutdownManager;
import com.wan.core.timer.bucket.TimeBuckets;
import com.wan.core.timer.round.IRoundWheel;
import com.wan.core.timer.task.TaskNode;
import com.wan.thread.ExecutorManager;
import io.netty.util.concurrent.DefaultThreadFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.time.Duration;
import java.time.LocalDateTime;
import java.util.Queue;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * 时间轮
 * @author wwh
 * @date 2022/6/27 20:27
 */
public class TimerWheel implements TimeHelper {
    private static final Logger logger = LoggerFactory.getLogger(TimerWheel.class);
    /** 名称 */
    private final String name;
    /** 状态 开|关*/
    private final AtomicBoolean status;
    /** 当前所属层级 */
    private final IRoundWheel roundWheel;
    /** 每个槽位间隔时间 */
    private final Duration duration;
    /** 上级时间轮 */
    private final TimerWheel parent;
    /** 下级时间轮 */
    private final TimerWheel child;
    /** 时间槽位 */
    private final TimeBuckets timeBuckets;
    /** 上一次转动时间 */
    private LocalDateTime lastTickTime;
    /** 时间轮调度线程 */
    private ScheduledExecutorService scheduledExecutorService;
    /** 调度任务 */
    private ScheduledFuture<?> scheduledFuture;
    /** 每次tick触发的任务 */
    private final Queue<Runnable> tickRunnable = new ConcurrentLinkedQueue<>();

    protected TimerWheel(IRoundWheel roundWheel, Duration duration,String name) {
        this(roundWheel,duration,null,name);
        start();
    }

    protected TimerWheel(IRoundWheel roundWheel, Duration duration, TimerWheel child,String name){
        this.status = new AtomicBoolean(false);
        this.roundWheel = roundWheel;
        this.duration = duration;
        this.child = child;
        this.name = name;
        this.timeBuckets = new TimeBuckets(this,roundWheel.getBucketNumber(),
                roundWheel.calculateCurrentBucket(LocalDateTime.now(),duration));
        //放最后
        this.parent = createParent();
    }

    public ExecutorService getExecutor(){
        return ExecutorManager.getExecutor(name,ExecutorManager.DEFAULT_CONFIG);
    }

    /**
     * 创建父级时间轮
     */
    private TimerWheel createParent(){
        IRoundWheel parentRound = roundWheel.getParent();
        if(parentRound == null){
            return null;
        }
        //当前间隔乘以槽数 = 父级时间轮的间隔
        Duration parentDuration = duration.multipliedBy(roundWheel.getBucketNumber());
        return new TimerWheel(parentRound,parentDuration,this,name);
    }

    public final ThreadLocal<Boolean> threadLocal = new ThreadLocal<>();
    /**
     * 启动时间轮
     */
    private synchronized void start(){
        if(status.compareAndSet(false,true)){
            this.lastTickTime = LocalDateTime.now();
            long period = this.duration.toMillis();
            scheduledExecutorService = Executors.newSingleThreadScheduledExecutor(new DefaultThreadFactory(name + "-schedule"));
            //绑定线程，用来隔离线程，只允许这条线程操作任务
            scheduledExecutorService.execute(()-> threadLocal.set(true));
            scheduledFuture = scheduledExecutorService.scheduleAtFixedRate(() -> {
                if(scheduledFuture != null && !scheduledFuture.isCancelled() && status.get()){
                    try {
                        //计算与上次转动时的间隔时间和要转动的槽数，并不限定每次转动一格，因为有误差时间和测试时改动时间的缘故
                        LocalDateTime now = LocalDateTime.now();
                        Duration between = Duration.between(lastTickTime, now);
                        long tickNumber = between.toMillis() / period;
                        for (long i = 0; i < tickNumber; i++) {
                            tick();
                        }
                        //时间偏移，防止向下取整后误差越来越大
                        Duration offset = between.minus(duration.multipliedBy(tickNumber));
                        this.lastTickTime = now.minus(offset);
                    } catch (Exception e) {
                        logger.error("时间轮[{}]线程执行异常",name, e);
                    }
                }
            }, period, period, TimeUnit.MILLISECONDS);
            logger.info("时间轮[{}]启动完成",name);
            ShutdownManager.dependOn(this::stop,"logger");
        }
    }

    /**
     * 滴答
     */
    private void tick(){
        Runnable runnable;
        while ((runnable = tickRunnable.poll()) != null){
            runnable.run();
        }
        if(timeBuckets.turn() && parent != null){
            parent.tick();
        }
    }

    /**
     * 添加任务,溢出会升级到上层时间轮
     */
    @Override
    public void addTask(TaskNode taskNode){
        LocalDateTime executeTime = taskNode.getExecuteTime();
        //过期，直接丢入当前层级执行
        if(taskNode.isExpired()){
            putCurrentTask(taskNode);
            return;
        }
        //当前时间轮的一个周期
        Duration roundDuration = duration.multipliedBy(roundWheel.getBucketNumber());
        //任务执行时间和现在的间隔
        Duration executeDuration = Duration.between(LocalDateTime.now(),executeTime);
        //满周期，升级到上层时间轮
        if(executeDuration.compareTo(roundDuration) >= 0 && parent != null){
            parent.addTask(taskNode);
            return;
        }
        //最小单位到毫秒计算（纳秒不考虑），除后向下取整，计算得出溢出周期数
        long round = executeDuration.toMillis() / roundDuration.toMillis();
        taskNode.setRound(round);
        putCurrentTask(taskNode);
    }

    /**
     * 任务降级，如果没有下一级，直接执行
     */
    public void degrade(TaskNode taskNode){
        if(child == null){
            taskNode.execute();
            return;
        }
        taskNode.cancel();
        child.putCurrentTask(taskNode);
    }

    /**
     * 添加任务到当前层级时间轮
     * tip:这个方法不会计算具体执行时间，只会计算任务在轮中的槽位，不代表实际执行时间
     */
    private void putCurrentTask(TaskNode taskNode){
        int bucketIndex = roundWheel.calculateCurrentBucket(taskNode.getExecuteTime(), duration);
        timeBuckets.addTask(bucketIndex,taskNode);
    }

    public IRoundWheel getRoundWheel() {
        return roundWheel;
    }

    protected void addTickRunnable(Runnable runnable){
        this.tickRunnable.offer(runnable);
    }

    /**
     * 查找最下层时间轮
     */
    public TimerWheel findMinWheel(){
        TimerWheel min = this;
        while (min.child != null){
            min = min.child;
        }
        return min;
    }

    public synchronized void stop(){
        if(this.status.compareAndSet(true,false)){
            scheduledFuture.cancel(true);
            scheduledExecutorService.shutdown();
            getExecutor().shutdown();
        }
    }

}
